package org.mcjug.locationmailer.service;

import org.mcjug.locationmailer.db.StatusMessageDatabaseHelper;
import org.mcjug.locationmailer.model.StatusMessage;
import org.mcjug.locationmailer.util.ExceptionUtil;
import org.mcjug.locationmailer.util.RunnableWithException;

import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;

//import android.util.//Log;

public class LocationService extends Service {
    private static final String TAG = LocationService.class.getSimpleName();
    private static final int LOCATION_TIMEOUT = 1000 * 60 * 2;

    public static final String NEW_LOCATION_INTENT_ACTION = "org.mcjug.locationmailer.NEW_LOCATION";
    public static final String RECEIVE_LOCATION_NOTIFICATIONS = "org.mcjug.locationmailer.RECEIVE_LOCATION_NOTIFICATIONS";
    public static final String SEND_LOCATION_NOTIFICATIONS = "org.mcjug.locationmailer.SEND_LOCATION_NOTIFICATIONS";

    private LocationManager locationManager;
    private ConnectivityManager connectivityManager;
    private ConnectivityReceiver connectivityReceiver;

    private Location currentLocation;
    private StatusMessageDatabaseHelper statusMessageHelper;
    private StatusMessage statusMessage;
    private boolean updateLocationRan;
    private LocationTimeoutTask locationTimeoutTask;
    private Handler handler;

    public int onStartCommand(Intent intent, int flags, int startId) {
        try {
            statusMessageHelper = new StatusMessageDatabaseHelper(getApplicationContext());
            handler = new Handler();
            locationTimeoutTask = new LocationTimeoutTask();
            handler.postDelayed(locationTimeoutTask, LOCATION_TIMEOUT);
            setupLocationUpdates();
        } catch (Throwable e) {
            // Log.e(TAG, "LocationService onStartCommand error", e);
            ExceptionUtil.exceptAway(this, e, "LocationService onStartCommand error: ", new RunnableWithException() {

                @Override
                public void run() throws Throwable {
                    // Log.d(TAG, "About to clean up after LocationService onStartCommand error");
                    cleanup();
                    LocationService.this.stopSelf();
                    // Log.d(TAG, "Done cleaning up after LocationService onStartCommand error");
                }
            });
        }

        return START_NOT_STICKY;
    }

    private void setupLocationUpdates() throws Throwable {
        // Clear out current location
        currentLocation = null;

        // Acquire a reference to the system Location Manager
        locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

        // Register location listener with the Location Manager to receive
        // location updates
        safeRequestLocationProvider(locationManager, LocationManager.GPS_PROVIDER, locationListener);
        safeRequestLocationProvider(locationManager, LocationManager.NETWORK_PROVIDER, locationListener);
    }

    private void safeRequestLocationProvider(LocationManager locationManager, String providerName,
            LocationListener locationListener) {
        try {
            locationManager.requestLocationUpdates(providerName, 10, 10, locationListener);
        } catch (IllegalArgumentException e) {
            // Ignore this exception, pray that somewhere down the line a valid call to this method is made.
        }
    }

    private void registerForConnectivityUpdates() throws Throwable {
        // Register to receive connectivity broadcasts
        connectivityReceiver = new ConnectivityReceiver();
        registerReceiver(connectivityReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
    }

    // Check connectivity status
    private boolean checkConnectivity() throws Throwable {
        // Acquire a reference to the system Connectivity Manager
        connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);

        boolean isConnected = false;

        NetworkInfo mobileNetworkInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
        if ((mobileNetworkInfo != null) && (mobileNetworkInfo.isConnected())) {
            isConnected = true;
        }

        if (!isConnected) {
            NetworkInfo wifiNetworkInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
            if ((wifiNetworkInfo != null) && (wifiNetworkInfo.isConnected())) {
                isConnected = true;
            }
        }

        if (!isConnected) {
            NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
            if ((activeNetworkInfo != null) && (activeNetworkInfo.isConnected())) {
                isConnected = true;
            }
        }

        return isConnected;
    }

    // Define a listener that responds to location updates
    private LocationListener locationListener = new LocationListener() {

        // Called when a new location is found by the location provider.
        public void onLocationChanged(Location newLocation) {
            try {
                currentLocation = newLocation;
                if (newLocation.getProvider().equals(LocationManager.GPS_PROVIDER)) {
                    locationManager.removeUpdates(this);
                    if (checkConnectivity()) {
                        updateLocation();
                    } else {
                        registerForConnectivityUpdates();
                    }
                }
            } catch (Throwable e) {
                // Log.e(TAG, "LocationService LocationListener onLocationChanged error", e);
                ExceptionUtil.exceptAway(LocationService.this, e,
                        "LocationService LocationListener onLocationChanged error:", new RunnableWithException() {

                            @Override
                            public void run() throws Throwable {
                                // Log.d(TAG,"About to clean up after LocationService LocationListener onLocationChanged error");
                                cleanup();
                                LocationService.this.stopSelf();
                                // Log.d(TAG,"Done cleaning up after LocationService LocationListener onLocationChanged error");
                            }
                        });
            }
        }

        public void onStatusChanged(String provider, int status, Bundle extras) {
        }

        public void onProviderEnabled(String provider) {
        }

        public void onProviderDisabled(String provider) {
        }
    };

    // Listener for Connectivity state
    private class ConnectivityReceiver extends BroadcastReceiver {
        public void onReceive(Context context, Intent intent) {
            try {
                NetworkInfo info = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
                if (info != null) {
                    NetworkInfo.State state = info.getState();
                    if (state == NetworkInfo.State.CONNECTED || state == NetworkInfo.State.CONNECTING) {
                        unregisterReceiver(connectivityReceiver);
                        connectivityReceiver = null;
                        updateLocation();
                    }
                }
            } catch (Throwable e) {
                // Log.e(TAG, "LocationService ConnectivityReceiver onReceive error", e);
                ExceptionUtil.exceptAway(LocationService.this, e,
                        "LocationService ConnectivityReceiver onReceive error:", new RunnableWithException() {

                            @Override
                            public void run() throws Throwable {
                                // Log.d(TAG,"About to clean up after LocationService ConnectivityReceiver onReceive error");
                                cleanup();
                                LocationService.this.stopSelf();
                                // Log.d(TAG,"Done cleaning up after LocationService ConnectivityReceiver onReceive error");
                            }
                        });
            }

        }
    }

    private class LocationTimeoutTask implements Runnable {
        public void run() {
            try {
                updateLocation();
            } catch (Throwable e) {
                // Log.e(TAG, "LocationService LocationTimeoutTask run error", e);
                ExceptionUtil.exceptAway(LocationService.this, e, "LocationService LocationTimeoutTask run error:",
                        new RunnableWithException() {

                            @Override
                            public void run() throws Throwable {
                                // Log.d(TAG, "About to clean up after LocationService LocationTimeoutTask run error");
                                cleanup();
                                LocationService.this.stopSelf();
                                // Log.d(TAG, "Done cleaning up after LocationService LocationTimeoutTask run error");
                            }
                        });
            }
        }
    }

    private void asyncMessageWork() {
        AsyncLocationServiceTask asyncLocationServiceTask = new AsyncLocationServiceTask(statusMessage,
                currentLocation, getApplicationContext(), statusMessageHelper, this);
        asyncLocationServiceTask.execute();
    }

    private void updateLocation() throws Throwable {
        if (!isUpdateLocationRan()) {
            setUpdateLocationRan(true);
            cleanup();

            asyncMessageWork();
            LocationService.this.stopSelf();
        }
    }

    public IBinder onBind(Intent arg0) {
        return null;
    }

    private void cleanup() throws Throwable {
        handler.removeCallbacks(locationTimeoutTask);
        locationManager.removeUpdates(locationListener);
        if (connectivityReceiver != null) {
            unregisterReceiver(connectivityReceiver);
        }
    }

    private synchronized boolean isUpdateLocationRan() throws Throwable {
        return updateLocationRan;
    }

    private synchronized void setUpdateLocationRan(boolean updateLocationRan) throws Throwable {
        this.updateLocationRan = updateLocationRan;
    }
}
